package zheezes.protocol.impl.gd;

import java.lang.reflect.Modifier;

import org.apache.log4j.Logger;

import zheezes.protocol.Frame;
import zheezes.protocol.impl.gd.bean.MeterData;
import zheezes.protocol.impl.gd.bean.SimpleMeterData;
import zheezes.util.ArrayUtil;
import zheezes.util.ByteUtil;


public abstract class GdFrame extends Frame {
	public static final int MAX_FRAME_LENGTH = (1 << 15);
	private static final byte AB_BIT = (1 << 6);

	protected GdConAddr rtua = new GdConAddr();
	protected GdMasterAddr msta = new GdMasterAddr();
	protected int fseq;
	protected int iseq;
	protected byte ctrl = gctrl();
	
	private static Logger logger = Logger.getLogger(GdFrame.class);
	
	public static GdFrame getFrame(byte ctrl) {
		GdFrame frame = null;
		if((ctrl & AB_BIT) != 0) {
			frame = new DenyFrame();
		} else {
			StringBuilder sb = new StringBuilder();
			sb.append(GdFrame.class.getPackage().getName() + ".");
			sb.append(String.format("Ctrl%02XFrame", ctrl));
			
			try {
				Class clazz = Class.forName(sb.toString());
				int modifiers = clazz.getModifiers();
				if (!Modifier.isAbstract(modifiers)) {
					frame = (GdFrame) clazz.newInstance();
				}
			} catch (Exception e) {
				logger.debug(e.getMessage(), e);
			}
		}
		if(frame != null) {
			frame.setCtrl(ctrl);
		}
		return frame;
	}
	
	public static GdFrame getFrame(byte[] buf, int offset, int max) {
		byte ctrl = buf[8];
		GdFrame frame = getFrame(ctrl);
		if(frame != null) {
			if(frame.size() > max - offset) {
				frame = null;
			} else {
				frame.setBytes(buf, offset, max);	
			}
		}
		return frame;
	}
	
	protected byte gctrl() {
		String className = getClass().getSimpleName().toLowerCase();
		if(className.startsWith("ctrl")) {
			return (byte) Integer.parseInt(className.substring(4, 6), 16);
		}
		return AB_BIT;
	}
	
	public int size() {
		return sizeOfData() + 13;
	}
	
	@Override
	public byte[] getBytes() {
		byte[] buf = new byte[size()];
		getBytes(buf, 0);
		
		return buf;
	}
	
	@Override
	public int getBytes(byte[] buf, int offset) {
		int idx = offset;
		buf[idx++] = 0x68;
		idx += rtua.getBytes(buf, idx);
		byte ms = msta.getData();
		buf[idx++] = (byte) (((fseq << 6) & 0xC0) | (ms & 0x3F));
		buf[idx++] = (byte) (((iseq << 5) & 0xE0) | ((fseq >> 2) & 0x1F));
		buf[idx++] = 0x68;
		buf[idx++] = ctrl;
		ByteUtil.stoc(buf, idx, (short) sizeOfData());// L
		idx += 2;
		idx += getData(buf, idx);
		buf[idx++] = (byte) ArrayUtil.sum(buf, 0, idx);
		buf[idx++] = 0x16;
		
		return idx - offset;
	}
	
	public int setBytes(byte[] buf, int offset, int max) {
		int idx = offset;
		idx++; // 0x68
		idx += rtua.setBytes(buf, idx, max);
		msta = new GdMasterAddr((byte) (buf[idx] & 0x3F));
		setFseq(((buf[idx] >> 6) & 0x03) | ((buf[idx + 1] << 2) & 0x7C));
		setIseq((buf[idx + 1] >> 5) & 0x07);
		idx += 2;//
		idx++; // 0x68
		ctrl = buf[idx++]; 
		short len = ByteUtil.ctos(buf, idx);
		idx += 2;
		idx += setData(buf, idx, idx + len);
		assert idx <= max;
		
		return idx - offset;
	}
	
	/* TODO, to fix one request and multi-response */
	public boolean match(Frame... frames) {
		for (Frame f : frames) {
			if ((f instanceof GdFrame) && (fseq == ((GdFrame) f).fseq)) {
			} else {
				return false;
			}
		}
		return true;
	}
	
	public abstract int sizeOfData();
	
	public abstract int getData(byte[] buf, int offset);
	
	public byte[] getData() {
		byte[] buf = new byte[sizeOfData()];
		getData(buf, 0);
		return buf;
	}
	
	public abstract int setData(byte[] buf, int offset, int max);

	public int setData(byte[] buf) {
		return setData(buf, 0, buf.length);
	}
	
	public boolean isDeny() {
		return (ctrl & AB_BIT) != 0;
	}

	public GdConAddr getRtua() {
		return rtua;
	}

	public void setRtua(GdConAddr rtua) {
		this.rtua = rtua;
	}

	public GdMasterAddr getMsta() {
		return msta;
	}

	public void setMsta(GdMasterAddr msta) {
		this.msta = msta;
	}

	public int getFseq() {
		return fseq;
	}

	public void setFseq(int fseq) {
		this.fseq = fseq;
	}

	public int getIseq() {
		return iseq;
	}

	public void setIseq(int iseq) {
		this.iseq = iseq;
	}

	public byte getCtrl() {
		return ctrl;
	}

	public void setCtrl(byte ctrl) {
		this.ctrl = ctrl;
	}
	
	public static void main(String[] args) {
		String sfrm = "68 82 41 88 99 06 15 68 92 18 00 01 01 00 01 40 01 10 01 23 89 11 28 82 00 00 06 40 10 81 44 00 00 01 00 51 16";
		//String sfrm = "68 82 41 88 99 A5 00 68 93 6F 00 01 01 01 10 01 13 01 27 04 12 00 00 00 32 54 55 00 70 62 55 00 76 57 55 00 36 59 55 00 98 60 55 00 59 61 55 00 67 61 55 00 75 61 55 00 82 61 55 00 96 61 55 00 62 62 55 00 65 62 55 00 FF FF FF FF 80 62 55 00 85 62 55 00 89 62 55 00 92 62 55 00 09 63 55 00 31 63 55 00 48 63 55 00 64 63 55 00 84 63 55 00 05 64 55 00 21 64 55 00 B5 04 70 16";
		// String sfrm = "68 82 40 06 00 20 00 68 A1 03 00 00 00 00 5C 16";
		StringBuilder sb = new StringBuilder();
		for (char c : sfrm.toCharArray()) {
			if (Character.isDigit(c) || Character.isLetter(c)) {
				sb.append(c);
			}
		}
		byte[] buf = ByteUtil.stoa(sb.toString().toCharArray());
		System.out.println(ArrayUtil.sprintf(buf, "%02X "));
		Ctrl92Frame frame = (Ctrl92Frame) getFrame(buf, 0, buf.length);
		MeterData[] datas = frame.getMetersData();
		for(MeterData md: datas) {
			System.out.println(((SimpleMeterData)md.getData()).getValue().toString());
		}
	}
}
